using System;
using System.Collections.Generic;
using System.Text;

namespace Chanutils
{

    public delegate void PeerNew(int PeerID, string PeerIP, int PeerPort);
    public delegate void PeerDeath(int PeerID);

    public delegate Peer GetPeerData(int PeerID);
    public delegate void PeerReceivedData(int PeerID, string FirstMessagePart, byte[] SecondMessagePart);

    public delegate byte[] GetReturnMsgForUnwantedPeer();
    public delegate byte[] GetReturnMsgForNewPeer();
    public delegate bool IsPeerOK(string PeerIP, int PeerPort);


    public class P2PMan
    {
        // with 5 peers apiece, let the chain go 4 deep..
        // that makes a theoretical 800 peers, but not all people will have 5 friends, and not all
        // will be unique.. and so on.
        private string sIP;
        private int iAvailablePort;

        // Events and..shit
        public event PeerNew OnNewPeerConnected;
        public event PeerDeath OnPeerDisconnect;
        public event PeerReceivedData OnPeerSentData;   // <-- THIS STILL NEEDS IMPLEMENTING >.<
        public event GetReturnMsgForUnwantedPeer OnUnwantedPeer;
        public event GetReturnMsgForNewPeer OnWantedPeer;
        public event IsPeerOK OnPeerCheckNeeded;

		// Sockets sockets sockets sockets
        private System.Net.Sockets.TcpListener tListener;
        private System.Net.Sockets.TcpClient[] tFriends;

        // Threads threads threads threads
        private bool bLetThreadRun = true;
        private bool bSigAbortOK = false;
        
		private System.Threading.Thread tBackgroundThread;

		// Thread-safing WHY YES THIS IS ONLY SLIGHTLY A HACK
        private bool bDontCountFriends = false;
		
        // Anti-spam anti-spam anti-spam anti-spam
        private System.DateTime dLastIPTime = System.DateTime.MinValue;
        
        // What is this faggotry
        private DSClient DS;

        
        //public P2PMan(string ExternalIP, int ListenPort, string DirectoryServer, int DServerPort, int MaxFriendNumber)
        public void StartP2P(string ExternalIP, int ListenPort, string DirectoryServer, int DServerPort, int MaxFriendNumber)
        {
            // Remember these, they're going to be pretty sodding important tbh
            sIP = ExternalIP;
            iAvailablePort = ListenPort;
            
            // Initiate the listener(!)
            tListener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Any, ListenPort);
            // Make sure it'll share between socks and stuffs...
            // Ohsh- NOT MONO PROOF D=
            //tListener.ExclusiveAddressUse = false;

            // Redim the tFriends array to be the MaxFriendNumber.
            tFriends = new System.Net.Sockets.TcpClient[MaxFriendNumber];

            // Directory server!
            DS = new DSClient(DirectoryServer, DServerPort, ListenPort, "dev"); // Hardcode "dev" as version during development.
            DS.OnRecieveIPList += new DS_OnRecieveIPList(GetMoreFriends_Reply);

            // Ask the directory server if our port it open...
            if (DS.SynchronousPortTest())
            	System.Console.WriteLine("Oh joyous fibrillations!\nPORT " + ListenPort.ToString() + " IS OPEN SAYS DIRECTORY SERVER " + DirectoryServer + ":" + DServerPort.ToString() + "\n");
            else
            	System.Windows.Forms.MessageBox.Show("Your currently listed Peer-to-Peer port '" + ListenPort.ToString() + "' is _NOT_ open!\r\n\r\nEither forward it, or change to a properly forwarded port in the user interface.\r\n\r\nFor hints on how to forward, also see the user interface. ^^", "Warning", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation);

            // Excellent!  Start listening for connection requests. ^^
            tListener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Any, ListenPort);
            tListener.Start();

            // Ok, so we're up!
            // Now, grab five ips...
            GetMoreFriends();
            
            // Run the thread.
            // (have to initialize _here_, as Poll() is non-static(!))
            tBackgroundThread = new System.Threading.Thread(new System.Threading.ThreadStart(Poll));
            tBackgroundThread.Start();
        }

        /// <summary>
        /// For every empty 'friend' slot, this requests a new IP from the directory server.
        /// </summary>
        public void GetMoreFriends()
        {

            // Ok.
            // First, make sure the main loop thang isn't running atm ...
            // TODO: CHECK THIS ACTUALLY WORKS, I THINK I WAS BEING SILLY WHEN I 'SIMPLIFIED' THIS
            bDontCountFriends = true;

            // How many?
            int iFriendsNeeded = tFriends.Length - CountFriends();

            // I do not know what this above code actually does 8-) I'll trust it's still useful in this context.
			// NOPE, REMOVED

            // Request friends! ^^
            DS.RequestIPs(iFriendsNeeded);

        }

        // Connection struct in DSClient.cs ^^
        /// <summary>
        /// This is the event handler, used to handle the reply of the 'GetMoreFriends()' command. It's going to be accessed
        /// pretty much exclusively by DSClient.cs, I think.
        /// </summary>
        /// <param name="Reply">An array of new, quite possibly shiny IP addresses</param>
        public void GetMoreFriends_Reply(Connection[] Reply)
        {
            // Well, server will never return more than we
            // asked for, so we'll just go for the number it asked for; not sure what
            // to do if the number we get is greater than locked clients anyway.
            //  WE CRASH AND BURN, BABY, YEEEEEEEAAAAAAAAAHHHHHHHH!11
            
            // Add!
            for (int i = 0; i < Reply.Length; i++)
            {

                // Find a free slot!
                for (int j = 0; j < tFriends.Length; j++)
                    // Free slot?
                    if (tFriends[j] == null)
                    {

                        // Connectzor!
                        // Is this all we'd need to do? HMM
                        tFriends[j] = new System.Net.Sockets.TcpClient(Reply[i].IP, Reply[i].Port);

                        break; // move to next reply.

                    }

                // If no free slots, it'll just skip it, so no worries, no error!
            }

            // And back to normal operation...
            dLastIPTime = System.DateTime.Now;
            bDontCountFriends = false;

        }

        /// <summary>
        /// Gracefully detaches from Peer-to-Peer activity, and the background thread.
        /// </summary>
        public void StopP2P()
        {
            bSigAbortOK = false;
            bLetThreadRun = false;

            // TODO: Deal with DSClient
            // TODO: _Gracefully_ detach from peer-to-peer activity(!)

            do
            {
                // Doodoodoo...
                // *twiddles thumbs*
                System.Threading.Thread.Sleep(10);
            }
            while (!bSigAbortOK);

            // GOGOGO
            tBackgroundThread = null;

            // Close all of the TcpClient connections... (if any)
            for (int i = tFriends.GetLowerBound(0); i <= tFriends.GetUpperBound(0); i++)
                if (tFriends[i] != null)
                {
                    if (tFriends[i].Client != null)
                        if (tFriends[i].Client.Connected)
                            //tFriends[i].Client.Disconnect(false);
                            tFriends[i].Client.Close();

                    tFriends[i].Close();
                    tFriends[i] = null;
                }

            // Stop listening.
            if (tListener != null) //&& tListener.Server != null)
            {
                tListener.Stop();
                tListener = null;
            }
        }

        /// <summary>
        /// The bulk of P2PMan, run as a seperate thread this services all of the peer-to-peer
        /// connections.
        /// </summary>
        private void Poll()
        {
            int i;

            do
            {

                // Poll Directory Server
                DS.Poll();

                #region (DATA RECEIVE CODE & RESULTING HOOK IMPLEMENTATION NEEDED) Poll existing connections
                    // for each existing connection...
                    for (i = tFriends.GetLowerBound(0); i <= tFriends.GetUpperBound(0); i++)
                    {
					    if (tFriends[i] != null)
					    {
						    // Any dead ones?
						    if (tFriends[i].Client != null)
						    {
							    if (!tFriends[i].Connected)
							    {
                                    OnPeerDisconnect(i);
								    tFriends[i].Close();
								    tFriends[i] = null;
							    }
							    else
							    {
								    // WE R KUNEKTED ^^
								    //
								    // ...
								    // Data buffer tiem
								    //


							    }
						    }
						    else
						    {
							    // Then wh-.. Oh never mind
                                OnPeerDisconnect(i);
							    tFriends[i] = null;
						    }
					    }
                    }
                #endregion

                #region (OK FOR NOW) Any new connection requests?
                // Any new connections being requested?
                    if (tListener.Pending())
                    {
                        // Er.  For every free 'friend' slot...
                        for (i = tFriends.GetLowerBound(0); i <= tFriends.GetUpperBound(0); i++)
                            if (tFriends[i] == null)
                                // Tis empty...
                                if (tListener.Pending())
                                {
                                    tFriends[i] = tListener.AcceptTcpClient();

                                    // check that this new client is ok with the thingy above us
                                    if (OnPeerCheckNeeded("0.0.0.0", 0))
                                    {
                                        // woah.  well whoops <.<
                                        SendBuffer(tFriends[i], OnUnwantedPeer());

                                        tFriends[i].Close();
                                        tFriends[i] = null;
                                    }
                                    else
                                    {
                                        // Awesome!
                                        // send PEERIDENTIFY message...
                                        SendBuffer(tFriends[i], OnWantedPeer());
                                        
                                        // Notify the hooking one about the existence of this thing, too...
                                        OnNewPeerConnected(i, "0.0.0.0", 0);
                                    }

                                }


                        // For each one of those who is left over...
                        do
                        {
                        	using (System.Net.Sockets.TcpClient tcTmp = tListener.AcceptTcpClient())
                        	{
	                            // Send a 'NOGOAWAY' message...
	                            SendBuffer(tcTmp, OnUnwantedPeer());
	
	                            //tcTmp.Client.Disconnect(false);
	                            tcTmp.Client.Close();
	                            tcTmp.Close();
	                            //tcTmp = null;
                        	}
                        }
                        while (tListener.Pending());
                    }
                #endregion

                #region (DONE) Do we not have all the peers we want atm?
                // < full capacity peer handling etcetcetc
                    if (!bDontCountFriends && System.DateTime.Now.Subtract(dLastIPTime).TotalSeconds >= 30)
                    {
                        // PEERS FIDDLING.
                        // DO WE HAVE FIVE, MOTHERFUCKER????????
                        //
                        //
                        //
                        if (CountFriends() < tFriends.Length)
                        {
                            // ... YEP
                            // The time is nao!1
                            GetMoreFriends();
                        }
                    }
                #endregion

                // Sleep.
                System.Threading.Thread.Sleep(15);
            }
            while (bLetThreadRun);

            // Acknowledge.
            bSigAbortOK = true;
        }

        private void SendBuffer(System.Net.Sockets.TcpClient tClient, byte[] xBuffer)
        {
            tClient.GetStream().Write(xBuffer, 0, xBuffer.Length);
        }

        /// <summary>
        /// Counts the amount of filled tFriends[] slots.
        /// </summary>
        /// <returns>An int representing the amount of filled tFriends[] slots</returns>
        private int CountFriends()
        {
            int iFriendCount = 0;

            for (int i = tFriends.GetLowerBound(0); i <= tFriends.GetUpperBound(0); i++)
                if (tFriends[i] != null)
                    iFriendCount++;

            return iFriendCount;
        }
    }
}
